package com.stm.bi.report.salesTrend;

import com.stm.base.dto.vo.DictVO;
import com.stm.base.provider.DictionaryProvider;
import com.stm.bi.core.util.DateUtils;
import com.stm.bi.core.util.SqlUtils;
import com.stm.bi.dto.query.DimensionDataGroupQry;
import com.stm.bi.dto.query.GroupField;
import com.stm.bi.dto.query.SalesTrendDetailLineChartQuery;
import com.stm.bi.dto.query.ods.OdsListQry;
import com.stm.bi.model.Chart;
import com.stm.bi.report.AbsChartQueryExe;
import com.stm.bi.report.util.DimensionPolicyUtils;
import com.stm.bi.repository.dataobject.ods.OdsDeptDO;
import com.stm.bi.repository.dataobject.ods.OdsShopDO;
import com.stm.bi.repository.ods.OdsDeptRepository;
import com.stm.bi.repository.ods.OdsShopRepository;
import com.stm.framework.dto.Response;
import com.stm.saas.api.PolicyDetailRemoteService;
import com.stm.saas.api.ProductConfigRemoteService;
import com.stm.saas.dto.vo.PolicyDetailVO;
import com.stm.saas.dto.vo.ProductConfigVO;
import com.stm.ucenter.enums.BusinessType;
import com.stm.ucenter.enums.DeptNature;
import com.stm.ucenter.enums.DeptType;
import com.stm.ucenter.enums.ShopStatus;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

import static com.baomidou.mybatisplus.core.toolkit.Constants.ASC;
import static com.baomidou.mybatisplus.core.toolkit.Constants.DESC;
import static com.stm.bi.constant.GroupTypeConstants.SUM;
import static com.stm.bi.constant.IndexConstants.*;
import static com.stm.bi.constant.SalesTrendConstants.*;
import static com.stm.bi.report.salesTrend.SalesTrendDomainService.getShopFormat;

@Component
public class SalesTrendDetailLineChartQueryExe extends AbsChartQueryExe<SalesTrendDetailLineChartQuery, Chart<List<List<Object>>>> {

    @Resource
    private ProductConfigRemoteService productConfigRemoteService;
    @Resource
    private DictionaryProvider dictionaryProvider;
    @Resource
    private OdsDeptRepository odsDeptRepository;
    @Resource
    private OdsShopRepository odsShopRepository;
    @Resource
    private PolicyDetailRemoteService policyDetailRemoteService;


    @Override
    public Chart<List<List<Object>>> query(Long orgId, String orgNo, SalesTrendDetailLineChartQuery query) {
        if (query.getDimension().matches("^\\d+$")) {
            List<PolicyDetailVO> policyDetailVOS = policyDetailRemoteService.selectByIds(List.of(Long.valueOf(query.getDimension()))).getData();
            query.setDimension(DimensionPolicyUtils.getPolicyByDimension(query.getDimension(), policyDetailVOS));
        }



        Response<ProductConfigVO> config = productConfigRemoteService.selectByOrgId(orgId);
        String salesType = "dealing";
        if(!config.isSuccess()){
            salesType = config.getData().getSalesDefinition();
        }

        List<String> dates = DateUtils.getReportDateList(query.getStartDate(), query.getEndDate(), query.getDateType().getValue());

        if(query.getLimit() == null){
            query.setLimit(5);
        }

        String key = query.getDimension().split("_")[0];
        return switch (key){
            case CUSTOMER     -> getCustomerChart(dates, orgNo, salesType, query);
            case ORGANIZATION -> getOrganizationChart(dates, orgNo, salesType, query);
            case PROD         -> getProdChart(dates, orgNo, salesType, query);
            case FORMAT       -> getFormatChart(dates, orgNo, orgId, salesType, query);
            case REGION       -> getRegionChart(dates, orgNo, orgId, salesType, query);
            default           -> throw new IllegalStateException("未知维度类型: " + key);
        };
    }

    private Chart<List<List<Object>>> getCustomerChart(List<String> dates, String orgNo, String salesType, SalesTrendDetailLineChartQuery query) {

        Chart<List<List<Object>>> chart = new Chart<>();
        List<List<Object>> data = new ArrayList<>();
        List<String> items = chart.getItems();

        DimensionDataGroupQry qry = new DimensionDataGroupQry()
                .setOrgNo(orgNo)
                .setDeptId(query.getDeptId())
                .setSalesType(salesType)
                .setDateType(query.getDateType().getValue())
                .setStartDate(dates.getFirst())
                .setEndDate(dates.getLast());
        String sql = SqlUtils.buildSql(qry, TABLE_NAME.get(query.getDimension()));

        List<Map> maps = jdbcService.selectList(sql, new Object[]{});

        items.add(NEW_CUSTOMERS_INDEX_NAME);
        items.add(OLD_CUSTOMERS_INDEX_NAME);
        BigDecimal sum1 = BigDecimal.ZERO;
        BigDecimal sum2 = BigDecimal.ZERO;
        for(String date : dates){
            Map map = maps.stream()
                    .filter(m -> Objects.equals(m.get(DATE_FIELD), date))
                    .findAny().orElse(new HashMap());

            List<Object> row = new ArrayList<>();
            row.add(DateUtils.getReportDateName(date, query.getDateType().getValue()));
            sum1 = sum1.add(map.get(NEW_CUSTOMERS_FILED) == null ? BigDecimal.ZERO : (BigDecimal) map.get(NEW_CUSTOMERS_FILED));
            sum2 = sum2.add(map.get(OLD_CUSTOMERS_FILED) == null ? BigDecimal.ZERO : (BigDecimal) map.get(OLD_CUSTOMERS_FILED));
            row.add(sum1);
            row.add(sum2);
            data.add(row);
        }

        chart.setDatas(data);
        chart.setItems(items);
        chart.setExtend(List.of());
        return chart;
    }

    private Chart<List<List<Object>>> getOrganizationChart(List<String> dates, String orgNo, String salesType, SalesTrendDetailLineChartQuery query) {

        Chart<List<List<Object>>> chart = new Chart<>();
        List<List<Object>> data = new ArrayList<>();
        List<String> items = chart.getItems();
        List<List<Object>> extendData = new ArrayList<>();

        List<OdsDeptDO> depts = odsDeptRepository.selectList(new OdsListQry().setOrgNo(orgNo));
        List<Long> deptIds = depts.stream()
                .filter(d -> Objects.equals(d.getParentId(), query.getDeptId())
                        && Objects.equals(d.getBusinessAttr(), BusinessType.MK.getValue())
                        && Objects.equals(d.getNature(), DeptNature.LINE.getValue()))
                .map(OdsDeptDO::getId).toList();

        DimensionDataGroupQry qry = new DimensionDataGroupQry()
                .setOrgNo(orgNo)
                .setStartDate(dates.getFirst())
                .setEndDate(dates.getLast())
                .setSalesType(salesType)
                .setDateType(query.getDateType().getValue())
                .setDeptIds(deptIds)
                .setGroupFields(List.of(GroupField.of(SALES_AMOUNT_FIELD, SUM)))
                .setGroupBy(List.of(DEPT_ID_FIELD))
                .setOrderBy(SALES_AMOUNT_FIELD)
                .setOrderType(query.getLimit() > 0 ? DESC : ASC)
                .setLimit(Pair.of(0, Math.abs(query.getLimit())));
        String itemSql = SqlUtils.buildSql(qry, TABLE_NAME.get(query.getDimension()));
        List<Map> itemMaps;
        if(deptIds.isEmpty()){
            itemMaps = new ArrayList<>();
        }else {
            itemMaps = jdbcService.selectList(itemSql, new Object[]{});
        }

        BigDecimal sum = itemMaps.stream()
                .map(m -> (BigDecimal) m.get(SALES_AMOUNT_FIELD))
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        List<Long> itemDeptIds = new ArrayList<>();
        for(Map sortMap : itemMaps){
            Long deptId = (Long) sortMap.get(DEPT_ID_FIELD);
            OdsDeptDO dept = depts.stream().filter(d -> d.getId().equals(deptId)).findAny().orElse(new OdsDeptDO());
            items.add(dept.getName());
            itemDeptIds.add(deptId);
            BigDecimal salesAmount = (BigDecimal) sortMap.get(SALES_AMOUNT_FIELD);
            extendData.add(List.of(dept.getName(), salesAmount, salesAmount.multiply(BigDecimal.valueOf(100)).divide(sum, 2, RoundingMode.HALF_UP)));
        }

        qry.setDeptIds(itemDeptIds)
                .setGroupFields(null)
                .setGroupBy(null)
                .setOrderBy(null)
                .setOrderType(null)
                .setLimit(null);

        String sql = SqlUtils.buildSql(qry, TABLE_NAME.get(query.getDimension()));

        List<Map> maps;
        if(itemDeptIds.isEmpty()){
            maps = new ArrayList<>();
        }else {
            maps = jdbcService.selectList(sql, new Object[]{});
        }

        for(String date: dates){
            List<Object> row = new ArrayList<>();
            row.add(DateUtils.getReportDateName(date, query.getDateType().getValue()));
            for(Long deptId : deptIds){
                Map map = maps.stream()
                        .filter(m -> Objects.equals(m.get(DATE_FIELD), date) && Objects.equals(m.get(DEPT_ID_FIELD), deptId))
                        .findAny().orElse(new HashMap());
                row.add(map.get(SALES_AMOUNT_FIELD) == null ? BigDecimal.ZERO : map.get(SALES_AMOUNT_FIELD));
            }

            data.add(row);
        }


        chart.setDatas(data);
        chart.setItems(items);
        chart.setExtend(extendData);

        return chart;
    }

    private Chart<List<List<Object>>> getProdChart(List<String> dates, String orgNo, String salesType, SalesTrendDetailLineChartQuery query) {

        String sumField = INDEX_FIELD_MAP.get(query.getType() == null ? SALES_INDEX : query.getType()).getFieldName();

        List<String> fields = FIELD_MAP.get(query.getDimension());
        String code = fields.getFirst();
        String name = fields.getLast();

        DimensionDataGroupQry qry = new DimensionDataGroupQry()
                .setDeptId(query.getDeptId())
                .setOrgNo(orgNo)
                .setStartDate(dates.getFirst())
                .setEndDate(dates.getLast())
                .setSalesType(salesType)
                .setDateType(query.getDateType().getValue())
                .setGroupFields(List.of(GroupField.of(sumField, SUM)))
                .setGroupBy(fields)
                .setOrderBy(sumField)
                .setOrderType(query.getLimit() > 0 ? DESC : ASC)
                .setLimit(Pair.of(0, Math.abs(query.getLimit())));

        String itemSql = SqlUtils.buildSql(qry, TABLE_NAME.get(query.getDimension()));
        List<Map> itemMaps = jdbcService.selectList(itemSql, new Object[]{});

        if (PROD_TYPE.equals(query.getDimension())) {
            List<DictVO> prodTypes = dictionaryProvider.selectList("style_category");
            for(Map map: itemMaps){
                String prodType = (String) map.get(STYLE_TYPE_FIELD);
                String prodTypeName = prodTypes.stream()
                        .filter(d -> d.getValue().equals(prodType))
                        .map(DictVO::getLabel)
                        .findAny().orElse(prodType);

                map.put(STYLE_TYPE_NAME_FIELD, prodTypeName);
            }
        }

        List<String> codes = itemMaps.stream()
                .map(m ->(String) m.get(code))
                .toList();

        Map<String, Object> otherConditions = new HashMap<>();
        otherConditions.put(fields.getFirst(), codes);
        List<String> groups = new ArrayList<>(fields);
        groups.add(DATE_FIELD);
        qry.setGroupBy(groups)
                .setOtherConditions(otherConditions)
                .setOrderBy(null)
                .setOrderType(null)
                .setLimit(null);

        String sql = SqlUtils.buildSql(qry, TABLE_NAME.get(query.getDimension()));
        List<Map> maps = jdbcService.selectList(sql, new Object[]{});

        if(PROD_TYPE.equals(query.getDimension())){
            fields = List.of(STYLE_TYPE_FIELD, STYLE_TYPE_NAME_FIELD);
        }
        return getTrendDetailChart(dates, query.getDateType().getValue(), itemMaps, maps, sumField, fields);
    }


    private Chart<List<List<Object>>> getFormatChart(List<String> dates, String orgNo, Long orgId, String salesType, SalesTrendDetailLineChartQuery query) {

        List<DictVO> formats;
        if(Objects.equals(FORMAT_CHANNEL, query.getDimension())){
            DictVO dict = new DictVO();
            dict.setLabel("其他");
            dict.setValue("-99");
            formats = List.of(dict);
        }else {
            List<DictVO> dicts = dictionaryProvider.selectList(DICT_MAP.get(query.getDimension()));
            formats = dicts.stream()
                    .peek(d -> d.getChildren().forEach(c ->
                            c.setLabel(d.getLabel() + "-" + c.getLabel())))
                    .flatMap(d -> d.getChildren().stream())
                    .toList();
        }

        List<OdsDeptDO> depts = odsDeptRepository.selectList(new OdsListQry().setOrgId(orgId));
        List<OdsShopDO> shops = odsShopRepository.selectList(new OdsListQry().setOrgId(orgId));

        List<Long> shopIds = depts.stream()
                .filter(d -> d.getType().equals(DeptType.SHOP.getValue())
                        && d.getAncestors().contains("" + query.getDeptId())
                        && d.getStatus().equals(ShopStatus.IN_OPERATION.getValue()))
                .map(OdsDeptDO::getId)
                .toList();

        //获取店铺业态分类MAP
        Map<String, List<Long>> formatMap = shops.stream()
                .filter(s -> shopIds.contains(s.getDeptId()))
                .filter(s -> getShopFormat(query.getDimension()).apply(s) != null)
                .collect(Collectors.groupingBy(
                        getShopFormat(query.getDimension()),
                        Collectors.mapping(
                                OdsShopDO::getDeptId,
                                Collectors.toList()
                        ))
                );

        List<String> fields = FIELD_MAP.get(query.getDimension());
        String code = fields.getFirst();
        String name = fields.getLast();

        DimensionDataGroupQry qry = new DimensionDataGroupQry()
                .setDeptIds(shopIds)
                .setOrgNo(orgNo)
                .setStartDate(dates.getFirst())
                .setEndDate(dates.getLast())
                .setSalesType(salesType)
                .setDateType(query.getDateType().getValue())
                .setGroupFields(List.of(GroupField.of(SALES_AMOUNT_FIELD, SUM)))
                .setGroupBy(List.of(DEPT_ID_FIELD));

        String itemSql = SqlUtils.buildSql(qry, TABLE_NAME.get(query.getDimension()));
        List<Map> items = jdbcService.selectList(itemSql, new Object[]{});

        List<Map> itemMaps = new ArrayList<>();
        formatMap.forEach((k,v) -> {
            DictVO dict = formats.stream()
                    .filter(d -> d.getValue().equals(k))
                    .findAny().orElse(new DictVO().setLabel("其他"));
            Map<String, Object> itemMap = new HashMap<>();
            itemMap.put(code, k);
            itemMap.put(name, dict.getLabel());
            BigDecimal sales = items.stream()
                    .filter(m -> v.contains((Long)m.get(DEPT_ID_FIELD)))
                    .map(m -> (BigDecimal) m.get(SALES_AMOUNT_FIELD))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            itemMap.put(SALES_AMOUNT_FIELD, sales);
            itemMaps.add(itemMap);
        });

        itemMaps.sort(Comparator.comparing(m -> (BigDecimal) m.get(SALES_AMOUNT_FIELD), Comparator.reverseOrder()));
        if(query.getLimit() < 0){
            itemMaps.reversed();
        }

        List<Map> finalItems = itemMaps.subList(0, Math.min(Math.abs(query.getLimit()), itemMaps.size()));

        List<Long> itemShopIds = finalItems.stream()
                .map(m -> (String) m.get(code))
                .flatMap(s -> formatMap.get(s).stream())
                .toList();

        qry.setDeptIds(itemShopIds)
                .setGroupBy(null)
                .setGroupFields(null);

        String sql = SqlUtils.buildSql(qry, TABLE_NAME.get(query.getDimension()));
        List<Map> maps = jdbcService.selectList(sql, new Object[]{});
        List<Map> finalMaps = new ArrayList<>();
        for(Map map: itemMaps){
            String key = (String)map.get(code);
            for(String date: dates){
                Map finalMap = new HashMap<>();
                finalMap.put(code, key);
                finalMap.put(name, map.get(name));
                BigDecimal sales = maps.stream()
                        .filter(m -> m.get(DATE_FIELD).equals(date))
                        .filter(m -> formatMap.get(key).contains((Long) m.get(DEPT_ID_FIELD)))
                        .map(m -> (BigDecimal) m.get(SALES_AMOUNT_FIELD))
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                finalMap.put(DATE_FIELD, date);
                finalMap.put(SALES_AMOUNT_FIELD, sales);
                finalMaps.add(finalMap);
            }
        }

        return getTrendDetailChart(dates, query.getDateType().getValue(), finalItems, finalMaps, SALES_AMOUNT_FIELD, FIELD_MAP.get(query.getDimension()));
    }


    private Chart<List<List<Object>>> getRegionChart(List<String> dates, String orgNo, Long orgId, String salesType, SalesTrendDetailLineChartQuery query) {

        List<String> fields = FIELD_MAP.get(query.getDimension());
        String code = fields.getFirst();
        String name = fields.getLast();

        DimensionDataGroupQry qry = new DimensionDataGroupQry()
                .setDeptId(query.getDeptId())
                .setOrgNo(orgNo)
                .setStartDate(dates.getFirst())
                .setEndDate(dates.getLast())
                .setSalesType(salesType)
                .setDateType(query.getDateType().getValue())
                .setGroupFields(List.of(GroupField.of(SALES_AMOUNT_FIELD, SUM)))
                .setGroupBy(fields)
                .setOrderBy(SALES_AMOUNT_FIELD)
                .setOrderType(query.getLimit() > 0 ? DESC : ASC)
                .setLimit(Pair.of(0, Math.abs(query.getLimit())));

        String itemSql = SqlUtils.buildSql(qry, TABLE_NAME.get(query.getDimension()));
        List<Map> itemMaps = jdbcService.selectList(itemSql, new Object[]{});

        itemMaps = itemMaps.subList(0, Math.min(Math.abs(query.getLimit()), itemMaps.size()));
        List<String> codes = itemMaps.stream()
                .map(m -> (String)m.get(code))
                .toList();

        for(Map map: itemMaps){
            if(Objects.equals(map.get(code), "110100000000")){
                map.put(name, "北京市");
            }

            if(Objects.equals(map.get(code), "120100000000")){
                map.put(name, "天津市");
            }

            if(Objects.equals(map.get(code), "310100000000")){
                map.put(name, "上海市");
            }

            if(Objects.equals(map.get(code), "500100000000")){
                map.put(name, "重庆市");
            }
        }

        Map<String, Object> otherConditions = new HashMap<>();
        otherConditions.put(code, codes);
        qry.setOtherConditions(otherConditions)
                .setOrderBy(null)
                .setOrderType(null)
                .setGroupBy(null)
                .setGroupFields(null)
                .setLimit(null);

        String sql = SqlUtils.buildSql(qry, TABLE_NAME.get(query.getDimension()));
        List<Map> maps = jdbcService.selectList(sql, new Object[]{});

        return getTrendDetailChart(dates, query.getDateType().getValue(), itemMaps, maps, SALES_AMOUNT_FIELD, fields);
    }

    public Chart<List<List<Object>>> getTrendDetailChart(List<String> dates,
                                                         String dateType,
                                                         List<Map> itemMaps,
                                                         List<Map> maps,
                                                         String sumField,
                                                         List<String> fields) {
        Chart<List<List<Object>>> chart = new Chart<>();
        List<List<Object>> data = new ArrayList<>();
        List<String> items = chart.getItems();
        List<List<Object>> extendData = new ArrayList<>();

        for(Map map: itemMaps){
            items.add(Objects.equals(map.get(fields.getFirst()), "-99") ? "其他" : (String) map.get(fields.getLast()) );
        }

        for(String date: dates){
            List<Object> row = new ArrayList<>();
            row.add(DateUtils.getReportDateName(date, dateType));

            for(Map map: itemMaps){
                String item = (String) map.get(fields.getFirst());
                BigDecimal value = maps.stream()
                        .filter(m -> Objects.equals(m.get(fields.getFirst()), item))
                        .filter(m -> Objects.equals(m.get(DATE_FIELD), date))
                        .map(m -> (BigDecimal) m.get(sumField))
                        .reduce(BigDecimal.ZERO, BigDecimal::add);
                row.add(value);
            }

            data.add(row);
        }

        extendData = getExtendData(itemMaps, sumField, fields.getLast());

        chart.setDatas(data);
        chart.setItems(items);
        chart.setExtend(extendData);
        return chart;
    }

    public List<List<Object>> getExtendData(List<Map> itemMaps, String sumField, String fieldName){
        List<List<Object>> extend = new ArrayList<>();
        BigDecimal sum = itemMaps.stream()
                .map(m -> (BigDecimal) m.get(sumField))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        for(Map map : itemMaps){
            List<Object> row = new ArrayList<>();
            BigDecimal value = (BigDecimal) map.get(sumField);
            String name = Objects.equals(map.get(fieldName), "-99") ? "其他" : (String) map.get(fieldName);
            if(name == null) {
                continue;
            }
            row = List.of(name, value, sum.doubleValue() == 0 ? BigDecimal.ZERO : value.multiply(BigDecimal.valueOf(100)).divide(sum, 2, RoundingMode.HALF_UP));
            extend.add(row);
        }

        return extend;
    }


    @Override
    public String getReportName() {
        return null;
    }
}
